Импорт библиотек
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import scipy.stats as stats
import math as mth
from plotly import graph_objects as go
from scipy import stats as st
from statsmodels.stats.proportion import proportions_ztest as p_ztest
import plotly.express as px
# Создайте DataFrame с данными
data = {
'Столбец1': [10, 10, 30, 40],
'Столбец2': [5, 15, 25, 35],
'Столбец3': [7, 17, 27, 37]
}
df = pd.DataFrame(data)
# Найдите название столбца с максимальным значением для каждой строки
df['Макс_Столбец'] = df.idxmax(axis=1)
# Выведите результат
print(df)
Столбец1 Столбец2 Столбец3 Макс_Столбец 0 10 5 7 Столбец1 1 10 15 17 Столбец3 2 30 25 27 Столбец1 3 40 35 37 Столбец1
Импорт данных
data = pd.read_csv('/datasets/logs_exp.csv', sep = '\t')
display(data.head(10))
print('Количество уникальных значений в столбцах:')
print(data.nunique())
data.info()
| EventName | DeviceIDHash | EventTimestamp | ExpId | |
|---|---|---|---|---|
| 0 | MainScreenAppear | 4575588528974610257 | 1564029816 | 246 |
| 1 | MainScreenAppear | 7416695313311560658 | 1564053102 | 246 |
| 2 | PaymentScreenSuccessful | 3518123091307005509 | 1564054127 | 248 |
| 3 | CartScreenAppear | 3518123091307005509 | 1564054127 | 248 |
| 4 | PaymentScreenSuccessful | 6217807653094995999 | 1564055322 | 248 |
| 5 | CartScreenAppear | 6217807653094995999 | 1564055323 | 248 |
| 6 | OffersScreenAppear | 8351860793733343758 | 1564066242 | 246 |
| 7 | MainScreenAppear | 5682100281902512875 | 1564085677 | 246 |
| 8 | MainScreenAppear | 1850981295691852772 | 1564086702 | 247 |
| 9 | MainScreenAppear | 5407636962369102641 | 1564112112 | 246 |
Количество уникальных значений в столбцах: EventName 5 DeviceIDHash 7551 EventTimestamp 176654 ExpId 3 dtype: int64 <class 'pandas.core.frame.DataFrame'> RangeIndex: 244126 entries, 0 to 244125 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 EventName 244126 non-null object 1 DeviceIDHash 244126 non-null int64 2 EventTimestamp 244126 non-null int64 3 ExpId 244126 non-null int64 dtypes: int64(3), object(1) memory usage: 7.5+ MB
Изменяем названия столбцов
data = data.rename(columns={'EventName': 'event', 'DeviceIDHash': 'user_id',
'EventTimestamp': 'date_time', 'ExpId': 'exp_id'})
#Проверка
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 244126 entries, 0 to 244125 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event 244126 non-null object 1 user_id 244126 non-null int64 2 date_time 244126 non-null int64 3 exp_id 244126 non-null int64 dtypes: int64(3), object(1) memory usage: 7.5+ MB
Приводим к нужному типу данных столбец с датой
data['date_time'] = pd.to_datetime(data['date_time'], unit='s')
#Проверка
display(data.head(3))
data.info()
| event | user_id | date_time | exp_id | |
|---|---|---|---|---|
| 0 | MainScreenAppear | 4575588528974610257 | 2019-07-25 04:43:36 | 246 |
| 1 | MainScreenAppear | 7416695313311560658 | 2019-07-25 11:11:42 | 246 |
| 2 | PaymentScreenSuccessful | 3518123091307005509 | 2019-07-25 11:28:47 | 248 |
<class 'pandas.core.frame.DataFrame'> RangeIndex: 244126 entries, 0 to 244125 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event 244126 non-null object 1 user_id 244126 non-null int64 2 date_time 244126 non-null datetime64[ns] 3 exp_id 244126 non-null int64 dtypes: datetime64[ns](1), int64(2), object(1) memory usage: 7.5+ MB
Поиск и удаление дубликатов
data['user_id'].count()
print('Дубликатов:',data.duplicated().sum())
print('Доля удалённых дубликатов:', round(data.duplicated().sum() / data['user_id'].count(),4))
data = data.drop_duplicates()
data.duplicated().sum()
Дубликатов: 413 Доля удалённых дубликатов: 0.0017
0
Добавляем столбец с датой без времени
data['date'] = pd.to_datetime(data['date_time'].dt.date)
#Проверка
data.head(3)
| event | user_id | date_time | exp_id | date | |
|---|---|---|---|---|---|
| 0 | MainScreenAppear | 4575588528974610257 | 2019-07-25 04:43:36 | 246 | 2019-07-25 |
| 1 | MainScreenAppear | 7416695313311560658 | 2019-07-25 11:11:42 | 246 | 2019-07-25 |
| 2 | PaymentScreenSuccessful | 3518123091307005509 | 2019-07-25 11:28:47 | 248 | 2019-07-25 |
Проверка пересечения пользователей в группах
data.groupby('user_id')['exp_id'].nunique().reset_index().query('exp_id > 1')
| user_id | exp_id |
|---|
Изучение количества пользователей и событий
Находим количество событий в логе
print('Всего событий в логе:', data['date'].count())
print('Количество видов событий в логе:', data['event'].nunique())
Всего событий в логе: 243713 Количество видов событий в логе: 5
Находим количество пользователей в логе
print('Всего пользователей в логе:', data['user_id'].nunique())
Всего пользователей в логе: 7551
Находим среднее количество событий на одного пользователя
print('В среднем, событий на пользователя:',round(data['date'].count() / data['user_id'].nunique(),3))
В среднем, событий на пользователя: 32.276
Визуализируем сколько событий в среднем приходится на пользователя
events_by_user = data.groupby('user_id').agg(event_count = ('event', 'count')).reset_index()
plt.figure(figsize=(15, 5))
plt.title('Распределение количества событий на пользователя')
plt.boxplot(x = events_by_user['event_count'], vert = False)
plt.xlabel('Количество событий')
plt.show()
print('95-й и 99-й перцентили:',np.percentile(events_by_user['event_count'], [95, 99]))
events_by_user['event_count'].describe()
95-й и 99-й перцентили: [ 89. 200.5]
count 7551.000000 mean 32.275593 std 65.154219 min 1.000000 25% 9.000000 50% 20.000000 75% 37.000000 max 2307.000000 Name: event_count, dtype: float64
Изучение периода данных
Период данных
print('Минимальное значение даты в данных:', data['date_time'].min())
print('Максимальное значение даты в данных:', data['date_time'].max())
Минимальное значение даты в данных: 2019-07-25 04:43:36 Максимальное значение даты в данных: 2019-08-07 21:15:17
Визуализация распределения логов по дате и времени
data_pivot = data.pivot_table(index='date',
values='event',
aggfunc='count',
columns='exp_id')
plt.figure(figsize = (15,5))
plt.plot(data_pivot)
plt.legend(data_pivot.columns)
plt.title('Распределение событий по дате и времени')
plt.ylabel('Количество событий')
plt.xlabel('Дата')
Text(0.5, 0, 'Дата')
Отбрасываем данные по июлю
#Сохраняем данные о количестве для расчета удалённой доли
ex_date = data['date'].count()
ex_users = data['user_id'].nunique()
#Отбрасываем данные и перезаписываем таблицу
data = data[data['date_time'] >= '2019-08-01']
print(data.info())
#Новый период данных для анализа
print('Новое начало периода данных', data['date_time'].min())
print('Новый конец периода данных', data['date_time'].max())
<class 'pandas.core.frame.DataFrame'> Int64Index: 240887 entries, 2828 to 244125 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event 240887 non-null object 1 user_id 240887 non-null int64 2 date_time 240887 non-null datetime64[ns] 3 exp_id 240887 non-null int64 4 date 240887 non-null datetime64[ns] dtypes: datetime64[ns](2), int64(2), object(1) memory usage: 11.0+ MB None Новое начало периода данных 2019-08-01 00:07:28 Новый конец периода данных 2019-08-07 21:15:17
print('Старое количество событий в логе:', ex_date)
print('Старое пользователей в логе:', ex_users)
print('Новое количество событий в логе:', data['date'].count())
print('Новых пользователей в логе:', data['user_id'].nunique())
print('Доля удалённых событий:', round(( (ex_date - data['date'].count()) / ex_date ),3))
print('Доля удалённых пользователей:',round(( (ex_users - data['user_id'].nunique()) / ex_users ),3))
Старое количество событий в логе: 243713 Старое пользователей в логе: 7551 Новое количество событий в логе: 240887 Новых пользователей в логе: 7534 Доля удалённых событий: 0.012 Доля удалённых пользователей: 0.002
Смотрим на распределение групп по количеству пользователей и событий
print('Распределение по количеству пользователей:')
print(data.groupby('exp_id').agg({'user_id': 'nunique'}))
print('Распределение по количеству событий:')
print(data.groupby('exp_id').agg({'user_id': 'count'}))
Распределение по количеству пользователей:
user_id
exp_id
246 2484
247 2513
248 2537
Распределение по количеству событий:
user_id
exp_id
246 79302
247 77022
248 84563
Изучим какие события есть в логах и как часто они встречаютя
events = (data.
groupby('event').
agg({'user_id': 'count'}).
reset_index().
rename(columns={'user_id' : 'total_events'}).
sort_values(by='total_events', ascending=False))
events
| event | total_events | |
|---|---|---|
| 1 | MainScreenAppear | 117328 |
| 2 | OffersScreenAppear | 46333 |
| 0 | CartScreenAppear | 42303 |
| 3 | PaymentScreenSuccessful | 33918 |
| 4 | Tutorial | 1005 |
Изучим пользователей по событиям, которые они совершали
user_events = (data.
groupby('event').
agg({'user_id': 'nunique'}).
reset_index().
rename(columns={'user_id' : 'total_users'}).
sort_values(by='total_users', ascending=False))
#Считаем долю пользователей совершивших события
user_events['percent'] = user_events['total_users'] / data['user_id'].nunique() * 100
user_events
| event | total_users | percent | |
|---|---|---|---|
| 1 | MainScreenAppear | 7419 | 98.473586 |
| 2 | OffersScreenAppear | 4593 | 60.963632 |
| 0 | CartScreenAppear | 3734 | 49.561986 |
| 3 | PaymentScreenSuccessful | 3539 | 46.973719 |
| 4 | Tutorial | 840 | 11.149456 |
Построим воронку событий, в которой отобразим долю перехода пользователей на следующий шаг, относительно предидущего.
#Исключаем событие урок
user_events = user_events[user_events['event'] != 'Tutorial']
#Строим воронку
fig = go.Figure(go.Funnel(y = user_events['event'],
x = user_events['total_users'],
textposition = 'inside',
textinfo = 'value + percent previous',
opacity = 0.65,
marker = {"color": ["deepskyblue", "lightsalmon", "tan", "teal", "silver"],
"line": {"width": [4, 2, 2, 3, 1, 1], "color": ["wheat", "wheat", "blue", "wheat", "wheat"]}},
connector = {"line": {"color": "royalblue", "dash": "dot", "width": 3}})
)
fig.update_layout(title_text='Воронка событий')
fig.show()
last_step = user_events.query('event == "PaymentScreenSuccessful"')['total_users']
first_step = user_events.query('event == "MainScreenAppear"')['total_users']
print('Доля пользователей доходящих до оплаты:', round(last_step[3]/first_step[1] * 100),'%')
#for event in user_events['event']:
# print(user_events[user_events['event'] == event]['total_users'] /
#user_events[user_events['event'] == event]['total_users'].shift() * 100)
print('Доля пользователей от предидущего шага:')
print(round(user_events['total_users'] / user_events['total_users'].shift() * 100))
Доля пользователей доходящих до оплаты: 48 % Доля пользователей от предидущего шага: 1 NaN 2 62.0 0 81.0 3 95.0 Name: total_users, dtype: float64
Изучим количество пользователей в группах 246(А),247(A),248(B)
data.groupby('exp_id').agg(count=('user_id', 'nunique'))
| count | |
|---|---|
| exp_id | |
| 246 | 2484 |
| 247 | 2513 |
| 248 | 2537 |
Проведём А/A экперимент для контрольных групп 246(А) и 247(A)
Проверим имеют ли группы 246(A) и 247(А) различия в статистических критериях
Гипотеза H0: Группы 246 и 247 не имеют разницы в статистических критериях
Гипотеза H1: Группы 246 и 247 имеют разницу в статистических критериях
alpha = 0.05
prov = p_ztest(count = data[data['exp_id'] == 246]['user_id'].nunique(),
nobs = data['user_id'].nunique(),
value = data[data['exp_id'] == 247]['user_id'].nunique()/data['user_id'].nunique())
if prov[1] < alpha:
print('Отвергаем нулевую гипотезу')
else:
print('Нулевую гипотезу не получилось отвергнуть')
Нулевую гипотезу не получилось отвергнуть
Создаём функцию для проведения дальнейших тестов
def test(d1, d2, event):
trials1 = d1['user_id'].nunique()
trials2 = d2['user_id'].nunique()
successes1 = d1[d1['event'] == event]['user_id'].nunique()
successes2 = d2[d2['event'] == event]['user_id'].nunique()
p1 = successes1/trials1
p2 = successes2/trials2
p_combined = (successes1 + successes2) / (trials1 + trials2)
difference = p1 - p2
z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/trials1 + 1/trials2))
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2
return p_value
Так как в дальнейших тестах будет происходить сравнение в разбивке по событиям,
то внесём поправку Шидака для 16 итераций, так как различных проведённых тестирований будет 16
shidak = 1 - pow((1 - alpha), (1/16))
Создаём срезы по группам
group_246 = data[data['exp_id'] == 246]
group_247 = data[data['exp_id'] == 247]
group_248 = data[data['exp_id'] == 248]
Проверим есть ли отличия в разбивке по событиям среди пользователей совершивших событие в группах 246(A) и 247(A)
Гипотеза H0: Между пользователями групп 246 и 247 совершивших событие нет различий в разбивке по событиям
Гипотеза H1: Между пользователями групп 246 и 247 совершивших событие есть различия в разбивке по событиям
n = 1
for event in user_events['event'].unique():
print('Событие:', event)
if test(group_246, group_247, event) < shidak:
print('p_value:', test(group_246, group_247, event))
print('Отвергаем нулевую гипотезу')
else:
print('p_value:', test(group_246, group_247, event))
print('Не получилось отвергнуть нулевую гипотезу')
Событие: MainScreenAppear p_value: 0.7570597232046099 Не получилось отвергнуть нулевую гипотезу Событие: OffersScreenAppear p_value: 0.2480954578522181 Не получилось отвергнуть нулевую гипотезу Событие: CartScreenAppear p_value: 0.22883372237997213 Не получилось отвергнуть нулевую гипотезу Событие: PaymentScreenSuccessful p_value: 0.11456679313141849 Не получилось отвергнуть нулевую гипотезу
Проверим есть ли отличия в разбивке по событиям среди пользователей совершивших событие в группах 246(A) и 248(B)
Гипотеза H0: Между пользователями групп 246 и 248 совершивших событие нет различий в разбивке по событиям
Гипотеза H1: Между пользователями групп 246 и 248 совершивших событие есть различия в разбивке по событиям
for event in user_events['event'].unique():
print('Событие:', event)
if test(group_246, group_248, event) < shidak:
print('p_value:', test(group_246, group_248, event))
print('Отвергаем нулевую гипотезу')
else:
print('p_value:', test(group_246, group_248, event))
print('Не получилось отвергнуть нулевую гипотезу')
Событие: MainScreenAppear p_value: 0.2949721933554552 Не получилось отвергнуть нулевую гипотезу Событие: OffersScreenAppear p_value: 0.20836205402738917 Не получилось отвергнуть нулевую гипотезу Событие: CartScreenAppear p_value: 0.07842923237520116 Не получилось отвергнуть нулевую гипотезу Событие: PaymentScreenSuccessful p_value: 0.2122553275697796 Не получилось отвергнуть нулевую гипотезу
Проверим есть ли отличия в разбивке по событиям среди пользователей совершивших событие в группах 247(A) и 248(B)
Гипотеза H0: Между пользователями групп 247 и 248 совершивших событие нет различий в разбивке по событиям
Гипотеза H1: Между пользователями групп 247 и 248 совершивших событие есть различия в разбивке по событиям
for event in user_events['event'].unique():
print('Событие:', event)
if test(group_247, group_248, event) < shidak:
print('p_value:', test(group_247, group_248, event))
print('Отвергаем нулевую гипотезу')
else:
print('p_value:', test(group_247, group_248, event))
print('Не получилось отвергнуть нулевую гипотезу')
Событие: MainScreenAppear p_value: 0.4587053616621515 Не получилось отвергнуть нулевую гипотезу Событие: OffersScreenAppear p_value: 0.9197817830592261 Не получилось отвергнуть нулевую гипотезу Событие: CartScreenAppear p_value: 0.5786197879539783 Не получилось отвергнуть нулевую гипотезу Событие: PaymentScreenSuccessful p_value: 0.7373415053803964 Не получилось отвергнуть нулевую гипотезу
Создаём срез с объединением групп 246 и 247
group_246_247 = data[data['exp_id'] != 248]
Проверим есть ли отличия в разбивке по событиям среди пользователей совершивших событие в группах 246(A)+247(A) и 248(B)
Гипотеза H0: Между пользователями групп 246(A)+247(A) и 248 совершивших событие нет различий в разбивке по событиям
Гипотеза H1: Между пользователями групп 246(A)+247(A) и 248 совершивших событие есть различия в разбивке по событиям
for event in user_events['event'].unique():
print('Событие:', event)
if test(group_246_247, group_248, event) < shidak:
print('p_value:', test(group_246_247, group_248, event))
print('Отвергаем нулевую гипотезу')
else:
print('p_value:', test(group_246_247, group_248, event))
print('Не получилось отвергнуть нулевую гипотезу')
Событие: MainScreenAppear p_value: 0.29424526837179577 Не получилось отвергнуть нулевую гипотезу Событие: OffersScreenAppear p_value: 0.43425549655188256 Не получилось отвергнуть нулевую гипотезу Событие: CartScreenAppear p_value: 0.18175875284404386 Не получилось отвергнуть нулевую гипотезу Событие: PaymentScreenSuccessful p_value: 0.6004294282308704 Не получилось отвергнуть нулевую гипотезу